iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 14
0
自我挑戰組

Angular2學習筆記系列 第 14

Angular2 實戰篇(七)

  • 分享至 

  • xImage
  •  

今天教大家如何實作抽換Service的功能,

這樣的寫法可以讓專案更增加彈性,實作細節是等到實際使用時才知道,

若是沒有實作出新的Service,還是可以使用原來的Service提供服務。

情境描述

我希望我的票卷頁面能夠提供匯率計算的功能

因為很多旅遊票卷通常是以旅遊當地的幣別做計價

但來網站訂票的人可能會希望看到換算為台幣為多少。

因此,我打算做一個ExchangeRateComponent

並且使用一個基本的ExchangeRateService做匯率計算。

這個ExchangeRateService是取用內部的JSON來取抓幣別匯率資料。

但是未來我希望能抽換ExchangeRateService,

改用APIExchangeRateService(使用線上讀取資料庫設定的幣別匯率資料)

而且不需修改ExchangeRateComponent的程式碼。

制作ExchangeRateComponent,並放置於CoreModule

使用AngularDoc Extension

在core目錄上右鍵選單ng g component

在輸入框輸入exchange-rate

會自動產生ExchangeRateComponent

並在CoreModule的declaration的區塊加好此元件

因為TicketModule要使用,

所以還需要手動在exports區塊加上ExchangeRateComponent做導出

接下來製作ExchangeRateComponent的元件內容

in exchange-rate.component.html

<span>
  <button class="btn btn-info" (click)="calculate()">匯率計算</button>
  <span *ngIf="resultMoney">{{to}}:{{resultMoney}}</span>
</span>

in exchange-rate.component.ts

import { Component, OnInit,Input,Output,EventEmitter } from '@angular/core';

@Component({
  selector: 'app-exchange-rate',
  templateUrl: './exchange-rate.component.html',
  styleUrls: ['./exchange-rate.component.scss']
})
export class ExchangeRateComponent implements OnInit {

  @Input()
  from:string; //原始幣別

  @Input()
  to:string = 'NTD'; //欲換算幣別

  @Input()
  sourceMoney:number; //原始金額

  resultMoney:number; //計算後金額

  constructor() { }

  ngOnInit() {
  }

  calculate(){
  	//TODO:稍後會使用ExchangeService來取得匯率資料
  	this.resultMoney = 0;
  }

}

制作ExchangeRateService,並放置於CoreModule

使用AngularDoc Extension

在core目錄上右鍵選單ng g service,在輸入框輸入exchange-rate

會自動產生ExchangeRateService

並在CoreModule的providers的區塊加好此服務

其服務的功能是取用JSON寫好的匯率幣別資料。

in exchange-rate.service.ts

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';

@Injectable()
export class ExchangeRateService {

  constructor(private http:Http) { }

  getExchangeRate(){
    return this.http
      .get("api/exchange-rate.json")
      .map((res:Response) => res.json());
  }

}

in api/exchange-rate.json

[
  {
    "key":"JPY-NTD",
    "value":0.3
  }
]

在ExchangeRateComponent中使用ExchangeRateService

in exchange-rate.component.ts

修改constructor注入ExchangeRateService

constructor(private exService: ExchangeRateService) { }

另外,改寫calculate()方法

  /**
   *  取得ExchangeRateService的幣別匯率資料來做匯率換算
   */
  calculate() {
    this.exService
      .getExchangeRate()
      .subscribe(
        (data:any[]) => {
          let rate:number = 0;
          // forEach找到符合的幣別匯率資料
          data.forEach((item)=>{
            let toRefKey = this.from + "-" + this.to;
            if(item.key === toRefKey){
              rate = item.value;
            }
          });
          this.resultMoney = this.sourceMoney * rate;
      });
  }

在票券頁面中使用ExchangeRateComponent

in ticket-list.component.html

<app-exchange-rate [from]="ticket.currency" [sourceMoney]="ticket.price">
</app-exchange-rate>

使用ExchangeRateService抓取的結果

改用APIExchangeRateService

假定線上資料庫有一個每日會抓取最新匯率的匯率資料表,

然後我們可以使用自己制作的API取得匯率資料,例如今天是2016-12-28,那麼就使用http://localhost:3000/rates/2016-12-28查詢今天的匯率資料。

我們在ticket的目錄下新增一個APIExchangeRateService,來使用此API。

in api-exchange-rate.service.ts

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';

@Injectable()
export class APIExchangeRateService {

  url:string = "http://localhost:3000/rates"

  constructor(private http:Http) { }

  getExchangeRate(){
    var today = new Date().toISOString().slice(0, 10);
    let queryUrl = this.url + "/" + today;
    return this.http
      .get(queryUrl)
      .map((res:Response) => res.json());
  }

}

接下來,我們在TicketModule設定使用此Service,

使用APIExchangeRateService取代掉原來的ExchangeRateService

  providers:[
    TicketService,
    {
      provide:ExchangeRateService,useClass: APIExchangeRateService
    }
  ]

我們完全不需要修改ExchageRateComponent這個元件。

它就能使用APIExchangeRateService。

這樣的寫法可以讓專案更增加彈性,實作細節是等到實際使用時才知道,若是沒有實作出APIExchangeRateService,還是可以使用原來的ExchangeRateService提供服務。

使用APIExchangeRateService抓取的結果

提供今天的Github專案進度檔案

還有API的專案更新-增加Rate功能


上一篇
Angular2 實戰篇(六)
下一篇
Angular2 踩雷系列文(一)
系列文
Angular2學習筆記19
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
KevinYang
iT邦新手 5 級 ‧ 2016-12-29 14:57:22

在寫service的時候,如果遇到function屬於Observable的,我會盡量讓subscriber在註冊後取道的值是不用在經過加工的. 意思是指

 /**
   *  取得ExchangeRateService的幣別匯率資料來做匯率換算
   */
  calculate() {
    this.exService
      .getExchangeRate()
      .subscribe(
        (data:any[]) => {
          let rate:number = 0;
          // forEach找到符合的幣別匯率資料
          data.forEach((item)=>{
            let toRefKey = this.from + "-" + this.to;
            if(item.key === toRefKey){
              rate = item.value;
            }
          });
          this.resultMoney = this.sourceMoney * rate;
      });
  }

這段subscribe後的動作,是否可以直接將尋找匯率的程式包在map裡,這樣子,subscribe時回傳的資料,就可以直接使用了. 範例如下

 /**
   *  取得ExchangeRateService的幣別匯率資料來做匯率換算
   */
  calculate() {
    this.exService
      .getExchangeRate()
      .map((data:any[]) => {
          let rate:number = 0;
          // forEach找到符合的幣別匯率資料
          data.forEach((item)=>{
            let toRefKey = this.from + "-" + this.to;
            if(item.key === toRefKey){
              rate = item.value;
            }
          })
          return rate;
      })
      .subscribe(
        (rate:number) => {          
          this.resultMoney = this.sourceMoney * rate;
      });
  }

這樣子的寫法,就更乾淨了,下一階段的重構, 就是把map裡的部分抽出來變成一個單獨的function之類的.
提供給你參考看看.

拉拉醬 iT邦新手 5 級 ‧ 2016-12-30 12:51:07 檢舉

你提供的寫法更好耶,這有點算是FP的思維嗎?
map用來「取匯率」
subscribe則是「用匯率」
這樣程式碼更有彈性,發生改A錯B的機率會更小!

KevinYang iT邦新手 5 級 ‧ 2016-12-30 14:21:54 檢舉

有點類似那樣子的觀念吧. 我只是覺得這樣子的寫法可以讓程式更容易維護.
其實這樣子的寫法,也有其他的使用情境

   getSource(){
      return this.http.get('....').map(res=> res.json()).share();
   }
   
   personName$ = this.getSource().map(data=>{
            return `${data.firstName} ${data.lastName}`;
       });   
   
   commecnts$ = this.getSource().map(data=>{
          return data.comments;
      });   

頁面

  <h2>{{ this.personName$ | async }}</h2>
  <ul>
      <li *ngFor="comment of this.comments$ | async">
      {{ comment }}
      </li>
  </ul>

我要留言

立即登入留言